package org.xx.armory.http;

import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;

import static java.net.URLDecoder.decode;
import static java.net.URLEncoder.encode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.indexOfIgnoreCase;
import static org.apache.commons.lang3.StringUtils.left;
import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
import static org.apache.commons.lang3.StringUtils.substring;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.Validators.notNull;

/**
 * Http相关的工具方法。
 */
public final class HttpUtils {
    private static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
    private final static String CHARSET_EQUALS = "charset=";

    /**
     * 禁止构造工具类的实例。
     */
    private HttpUtils() {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取用于解析和格式化HTTP日期的日期格式化器。
     *
     * @return 用于解析和格式化HTTP日期的日期格式化器。
     * @see <a href="https://tools.ietf.org/html/rfc2616#section-3.3">RF2616 Section 3.3</a>
     */
    private static DateFormat getFormat() {
        final SimpleDateFormat ret = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
        ret.setTimeZone(TimeZone.getTimeZone("GMT"));
        return ret;
    }

    /**
     * 按照RFC822解析HTTP请求头部的日期类型值。
     *
     * @param s
     *         待解析的字符串值。
     * @return 解析后的日期类型值，如果参数 {@code s}是{@code null}或者只包含空白字符则返回 {@code null}。
     * @throws ParseException
     *         如果参数 {@code s} 不是空字符串，但是也无法成功解析为日期值。
     */
    public static Date parseHttpDate(
            String s
    )
            throws ParseException {
        s = trimToEmpty(s);
        if (!s.isEmpty()) {
            return getFormat().parse(s);
        } else {
            return null;
        }
    }

    /**
     * 按照RFC822格式化HTTP响应头部的日期类型值。
     *
     * @param date
     *         待格式化的日期值。
     * @return 格式化后的字符串。
     * @throws IllegalArgumentException
     *         如果参数 {@code d} 是 {@code null}。
     */
    public static String formatHttpDate(
            Date date
    ) {
        notNull(date, "date");

        return getFormat().format(date);
    }

    /**
     * 从Content-Type头的内容中解析请求字符编码。
     *
     * @param contentType
     *         Content-Type头的内容。
     * @return 解析结果，第一个元素是内容类型（不包含Charset），第二个元素是请求字符编码。
     */
    public static String[] parseContentType(
            String contentType
    ) {
        contentType = trimToEmpty(contentType);
        if (!contentType.isEmpty()) {
            final int indexOfCharset = indexOfIgnoreCase(contentType, CHARSET_EQUALS);
            if (indexOfCharset != -1) {
                final int indexOfEncoding = indexOfCharset + CHARSET_EQUALS.length();
                String contentType1 = left(contentType, indexOfCharset).trim();
                if (contentType1.endsWith(";")) {
                    contentType1 = substring(contentType1, 0, -1);
                }
                final String encoding = substring(contentType, indexOfEncoding).trim();
                return new String[]{contentType1, encoding};
            } else {
                return new String[]{contentType, ""};
            }
        }

        return new String[]{"", ""};
    }

    /**
     * 判断指定的内容类型是否表示 {@literal Multipart} ，这种内容类型用于上传文件。
     *
     * @param contentType
     *         指定的内容类型，自动去掉首尾空格。
     * @return 如果此内容类型表示 {@literal Multipart} 则返回 {@code true}，否则返回 {@code false}。
     */
    public static boolean isMultipartRequest(
            String contentType
    ) {
        return startsWithIgnoreCase(trimToEmpty(contentType), "multipart/");
    }

    /**
     * 判断指定的内容类型是否表示 {@literal Form-Urlencoded} ，这种内容类型用于提交表单。
     *
     * @param contentType
     *         指定的内容类型，自动去掉首尾空格。
     * @return 如果此内容类型表示 {@literal Form-Urlencoded} 则返回 {@code true}，否则返回 {@code false}。
     */
    public static boolean isFormUrlEncodedRequest(
            String contentType
    ) {
        return containsIgnoreCase(trimToEmpty(contentType), "form-urlencoded");
    }

    /**
     * 将{@literal Form URL}方式编码的字符串解析为{@code Map<String, String>}对象。
     *
     * <p>
     * 将字符串进行URLDecode时使用{@code UTF-8}编码
     * </p>
     *
     * @param s
     *         按照{@literal Form URL}方式编码的字符串
     * @return 解析得到的 {@code Map<String, String>} 对象。
     * @throws UnsupportedEncodingException
     *         如果无法使用{@code UTF-8}编码进行URLDecode。
     * @see java.net.URLDecoder#decode(String, String)
     */
    public static Map<String, String> decodeFormEncoded(
            String s
    )
            throws UnsupportedEncodingException {
        final Map<String, String> result = new TreeMap<>();
        s = trimToEmpty(s);
        if (!s.isEmpty()) {
            final String[] segments = s.split("&");
            for (final String seg : segments) {
                final String[] kv = seg.split("=");
                if (kv.length < 2) {
                    continue;
                }

                final String key = decode(trimToEmpty(kv[0]), "UTF-8");
                final String value = decode(trimToEmpty(kv[1]), "UTF-8");

                result.put(key, value);
            }
        }

        return result;
    }

    /**
     * 将{@code Map<String, String>}对象按照{@literal Form URL}方式编码为字符串。
     *
     * <p>
     * 将字符串进行URLEncode时使用{@code UTF-8}编码
     * </p>
     *
     * @param parameters
     *         待编码的映射。
     * @return 编码后得到的字符串，不包含头部的问号，便于和其它字符串拼接。
     * @throws UnsupportedEncodingException
     *         如果无法使用{@code UTF-8}编码进行URLEncode。
     * @see java.net.URLEncoder#encode(String, String)
     */
    public static String encodeFormEncoded(
            Map<String, String> parameters
    )
            throws UnsupportedEncodingException {
        if (parameters == null || parameters.size() == 0) {
            return "";
        }

        final StringBuilder result = new StringBuilder();
        for (final Map.Entry<String, String> entry : parameters.entrySet()) {
            final String key = trimToEmpty(entry.getKey());
            final String value = trimToEmpty(entry.getValue());
            if (!key.isEmpty() && !value.isEmpty()) {
                if (result.length() > 0) {
                    result.append("&");
                }

                result.append(encode(key, UTF_8.name())).append("=").append(encode(value, UTF_8.name()));
            }
        }

        return result.toString();
    }
}
